/*
 * Copyright (c) 2015 tingpan
 * Copyright 2012-2015 Senscom.cn
 *    tingpan <smbx-ztbz@cnblog.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>  //混杂设备
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/delay.h>  //mdelay
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_gpio.h>

#include <linux/kfifo.h>
#include <linux/interrupt.h>
#include <linux/irq.h>

#include <linux/types.h>  //u8

#include <linux/ioctl.h>


#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/init.h>  
#include <linux/proc_fs.h>  
#include <linux/jiffies.h>  
#include <asm/uaccess.h>  

#include <asm/addrspace.h>
#include <asm/tc3162/tc3162.h>
#include <asm/tc3162/ledcetrl.h>





#define PCF8574_DEBUG    1

#if (PCF8574_DEBUG == 1)
#define PRK(...) printk(__VA_ARGS__)
#else 
#define PRK(...) 
#endif

#define DRV_NAME    "pcf8574"
#define DRV_DESC    "use i2c to extend i/o" 
#define DRV_VERSION    "0.1.0"

//#define PCF8574_NODE_NAME DRV_NAME

/*
#define  GPIO_SDA  8
#define  GPIO_SLC  7
*/


#define  GPIO_SDA  6
#define  GPIO_SLC  4


#define  GPIO_SDA_1  8
#define  GPIO_SLC_1  7


int scl_pin = GPIO_SLC;
int sda_pin = GPIO_SDA;

static struct mutex pcf8574_lock;

static DEFINE_MUTEX(pcf8574_lock);

//pcf8574
// 发送I2C启动位 


#define  GPIO_BOMA_1  28
#define  GPIO_BOMA_2  23
#define  GPIO_BOMA_3  5
#define  GPIO_BOMA_4  25



#define LED_GET_GPIO_CTRL(x) ((x>31)?(regRead32(CR_GPIO_CTRL1)&(1<<(x-32))):(regRead32(CR_GPIO_CTRL)&(1<<x)))
#define LED_GET_GPIO_DATA(x) ((x>31)?(regRead32(CR_GPIO_DATA1)&(1<<(x-32))):(regRead32(CR_GPIO_DATA)&(1<<x)))


#define REG_CLRBITS(reg, bits) (regWrite32(reg, regRead32(reg) & ~(bits)))
#define REG_SETBITS(reg, bitsmask, bits) (regWrite32(reg, (regRead32(reg) & ~(bitsmask)) | (bits)))


#define GPIO_INPUT_ENABLE(x)		do { \	
									if(x > 31){							\
										if(x > 47)						\
											REG_CLRBITS(CR_GPIO_CTRL3, (0x00000003 << ((x-48)* 2)));	\
										else							\
											REG_CLRBITS(CR_GPIO_CTRL2, (0x00000003 << ((x-32)* 2))); \
										REG_CLRBITS(CR_GPIO_ODRAIN1, (0x00000001 << (x-32))); \
									}									\
									else{								\
										if(x > 15)						\
											REG_CLRBITS(CR_GPIO_CTRL1,(0x00000003 << ((x-16)* 2)));	\
										else							\
											REG_CLRBITS(CR_GPIO_CTRL,(0x00000003 << (x* 2)));	\
										REG_CLRBITS(CR_GPIO_ODRAIN,(0x00000001 << (x))); \
									}									\
				} while(0);



#define GPIO_OTPUT_MODE(x)		do { 	if(x > 15){ 					\
											REG_SETBITS(CR_GPIO_CTRL1,0x3<<((x-16)*2), 1<<((x-16)*2)); \
										}else{							\
											REG_SETBITS(CR_GPIO_CTRL,0x3<<((x)*2), 1<<((x)*2)); \
										}								\
										REG_SETBITS(CR_GPIO_ODRAIN, 0x1<<(x), 1<<(x)); \
                     		} while(0);


#define SDA_INPUT_MODE() do {\
		                      REG_CLRBITS(CR_GPIO_CTRL,(0x00000003 << (sda_pin* 2)));	\
							  REG_CLRBITS(CR_GPIO_ODRAIN,(0x00000001 << (sda_pin))); \
						   }while(0);


#define SDA_OUTPUT_MODE() do {\
	                             REG_SETBITS(CR_GPIO_CTRL,0x3<<((sda_pin)*2), 1<<((sda_pin)*2)); \
								 REG_SETBITS(CR_GPIO_ODRAIN, 0x1<<(sda_pin), 1<<(sda_pin)); \
						   }while(0);



                           
/*

regWrite32(CR_GPIO_ODRAIN,regRead32(CR_GPIO_ODRAIN) & ~(0x00000001<<(GPIO_SDA))); \
                          

#define  HIGH_SDA()  do { SDA_OUTPUT_MODE();regWrite32(CR_GPIO_DATA, regRead32(CR_GPIO_DATA)|(1<<GPIO_SDA));} while(0);
#define  LOW_SDA()  do { SDA_OUTPUT_MODE();regWrite32(CR_GPIO_DATA, regRead32(CR_GPIO_DATA)|~(1<<GPIO_SDA));} while(0);
#define  HIGH_SCL()  do { regWrite32(CR_GPIO_DATA, regRead32(CR_GPIO_DATA)|(1<<GPIO_SLC));} while(0);
#define  LOW_SCL()  do { regWrite32(CR_GPIO_DATA, regRead32(CR_GPIO_DATA)& ~(1<<GPIO_SLC));} while(0);
*/


#define HIGH_SDA() do { SDA_OUTPUT_MODE(); regWrite32(CR_GPIO_DATA,regRead32(CR_GPIO_DATA) | (1<<sda_pin)); } while (0);
#define LOW_SDA() do { SDA_OUTPUT_MODE(); regWrite32(CR_GPIO_DATA,regRead32(CR_GPIO_DATA) & ~(1<<sda_pin)); } while (0);
#define HIGH_SCL() do {regWrite32(CR_GPIO_DATA,regRead32(CR_GPIO_DATA) | (1<<scl_pin));} while(0);
#define LOW_SCL() do {regWrite32(CR_GPIO_DATA,regRead32(CR_GPIO_DATA) & ~(1<<scl_pin));} while(0);


static void gpio_direction_input_value(int pin)
{
    SDA_INPUT_MODE()

}

static int gpio_get_value_ok(int pin)
{
    if((regRead32(CR_GPIO_DATA) & (1<<sda_pin)) == 0)
        return 0;
	else 
		return 1;
}



static void gpio_direction_output_value(int pin,int value)
{
  
   if(pin== sda_pin)
   	{
       if(value == 0)
       	{
       	   LOW_SDA();
       	}else{
			   HIGH_SDA();

       	}
   	} 
    else {
		if(value == 0)
		 {
			LOW_SCL();
		 }else{
				HIGH_SCL();
		
		 }

   	}
	return ;

}


static void gpio_set_value_ok(int pin,int value)
{

	if(pin== sda_pin)
	 {
		if(value == 0)
		 {
			LOW_SDA();
		 }else{
				HIGH_SDA();
	
		 }
	 } 
	 else {
		 if(value == 0)
		  {
			 LOW_SCL();
		  }else{
				 HIGH_SCL();
		 
		  }
	
	 }

	 return ;

}



static void i2c_start(void)
{
    //sda_pin=1; 
    gpio_direction_output_value(sda_pin, 1);
    //scl_pin=1; 
    gpio_direction_output_value(scl_pin, 1);
    //delay_us(5);
    udelay(4);
    //SDA=0; 
    gpio_set_value_ok(sda_pin, 0);
    //delay_us(5);
    udelay(4);
    //SCL=0;
    gpio_set_value_ok(scl_pin, 0);
    //delay_us(5);
    udelay(4);
}

// 发送I2C停止位 
static void i2c_stop(void)
{
    //SDA=0;
    gpio_direction_output_value(sda_pin, 0);
    //SCL=1;
    gpio_direction_output_value(scl_pin, 1);
    //delay_us(5);
    udelay(4);
    //SDA=1;
    gpio_set_value_ok(sda_pin, 1);
    //delay_us(5);
    udelay(4);
    //SCL=0;
    gpio_set_value_ok(scl_pin, 1);
    //delay_us(5);
    udelay(4);
}

// 发送BIT0  
static void i2c_send_bit_0(void)
{
    //SDA=0;
    gpio_direction_output_value(sda_pin, 0);
    //SCL=1;
    gpio_direction_output_value(scl_pin, 1);
    //delay_us(5);
    udelay(4);
    //SCL=0;
    gpio_set_value_ok(scl_pin, 0);
    //delay_us(5);
    udelay(4);
}

// 发送BIT1 
static void i2c_send_bit_1(void)
{
    //SDA=1;
    gpio_direction_output_value(sda_pin, 1);
    //SCL=1;
    gpio_direction_output_value(scl_pin, 1);
    //delay_us(5);
    udelay(4);
    //SCL=0;
    gpio_set_value_ok(scl_pin, 0);
    //delay_us(5);
    udelay(4);
}

static int i2c_recv_ack(void)
{
    int tmp;
    //SDA=1;
    gpio_direction_output_value(sda_pin, 1);
	gpio_direction_input_value(sda_pin);
    //SCL=1;
  //  
    //delay_us(5);
    udelay(4);
    //F0=SDA;
    gpio_direction_output_value(scl_pin, 1);
	udelay(4);
	
    tmp = gpio_get_value_ok(sda_pin);
    //delay_us(5);
    udelay(4);
    //SCL=0;
    gpio_direction_output_value(scl_pin, 0);
    //delay_us(5);
    udelay(4);
    //if(F0==1) return 1;
    if (tmp == 1) return 1;

    return 0;
}



static void i2c_send_ack(void)
{
    //SDA=0;
    gpio_direction_output_value(sda_pin, 0);
    //SCL=1;
    gpio_direction_output_value(scl_pin, 1);
    //delay_us(5);
    udelay(4);
    //SDA=0;
    gpio_set_value_ok(sda_pin, 0);
    //delay_us(5);
    udelay(4);
    //SCL=0;
    gpio_set_value_ok(scl_pin, 0);
    //delay_us(5);
    udelay(4);

}

static void i2c_send_noack(void)
{    
    //SDA=1;
    gpio_direction_output_value(sda_pin, 1);
    //SCL=1;
    gpio_direction_output_value(scl_pin, 1);
    //delay_us(5);
    udelay(4);
    //SDA=1;
    gpio_set_value_ok(sda_pin, 1);
    //delay_us(5);
    udelay(4);
    //SCL=0;
    gpio_set_value_ok(scl_pin, 0);
    //delay_us(5);
    udelay(4);

}

static void i2c_write_byte(u8 data)
{
    u8 i;
    for (i=0; i<8; i++) {
        if ((data<<i) & 0x80)
                i2c_send_bit_1();
            else
                i2c_send_bit_0();
    }
	
}

static u8 i2c_read_byte(void)
{
    u8 data = 0;
    u8 i;
    int tmp;
    for (i=0; i<8; i++) {
        //SDA=1;  
   //     gpio_direction_output_value(sda_pin, 1);  
        //SCL=1;   
        gpio_direction_output_value(scl_pin, 1); 
        //delay_us(5);
        udelay(4);
        //F0=SDA;
        gpio_direction_input_value(sda_pin);
        tmp = gpio_get_value_ok(sda_pin);
        //delay_us(5);
        udelay(4);
        //SCL=0;
        gpio_set_value_ok(scl_pin, 0);
		udelay(4);
        if (tmp == 1) {
            data = data<<1;
            data = data | 0x01;
        } else
            data = data<<1;
    }
    return data;
}

// 控制PCF8574引脚电平
static int pcf8574_write(u8 val)
{
      int acktmp = 1; 
      i2c_start();
      i2c_write_byte(0x40);//写控制指令 0x20<<1 R/W
      acktmp = i2c_recv_ack();
      if (acktmp == 1) {
        PRK("i2c_recv_ack fail\n");
        //return -1;
    }
      i2c_write_byte(val);                   
      acktmp = i2c_recv_ack();
      if (acktmp == 1) {
        PRK("i2c_recv_ack fail\n");
        //return -1;
    }
      i2c_stop();
      if (acktmp == 1) return -1;
      return 0;
}

// 读出PCF8574引脚电平
static u8 pcf8574_read(u8 regAddr)
{    
    int acktemp = 1;
    u8 rddata = 0;
	int temp;
    i2c_start();

    i2c_write_byte(0xa0);//读控制指令

    temp =  i2c_recv_ack();


	i2c_write_byte(regAddr);//读控制指令

	
    i2c_recv_ack();

    i2c_start();
	
	i2c_write_byte(0xa1);//读控制指令

	i2c_recv_ack();

    rddata = i2c_read_byte();

    i2c_send_noack();

    i2c_stop();
    return rddata;
}

static ssize_t op_pcf8574_read(struct file *file, char __user * dat, 
                                    size_t len, loff_t *loff)
{
    int err = 0;
    u8 result;
    err = !access_ok(VERIFY_WRITE, (void __user *)dat, _IOC_SIZE(len));//用到len，分配指定大小空间
    if (err) {
        PRK(KERN_INFO " access not allowed!\n");
        return -EFAULT;
    }    
    result = pcf8574_read(0x1);
    __copy_to_user(dat, &result, 1);
    return 0;
}

static ssize_t op_pcf8574_write(struct file *file, char __user * dat, 
                                    size_t len, loff_t *loff)
{


    u8 wrdata = 0;
    if(!copy_from_user(&wrdata, dat, len))
    {
        printk("jiangyibo %d\n",wrdata);

	    return 0;
        /*
        if (!pcf8574_write(wrdata)) {
            PRK("op_sensorid_write %d success!\n",wrdata);
            return 0;
        } else {
            PRK("op_sensorid_write %d fail!\n",wrdata);
            return -1;
        }
        */
    }  
    else
         return -1;

        
}






static int op_pcf8574_init(void )
{
	GPIO_OTPUT_MODE(GPIO_SLC)
	GPIO_OTPUT_MODE(GPIO_SLC_1)
	return 0;
}



								

static int op_pcf8574_open(struct inode *inode, struct file *file)
{
    int err;

    err = gpio_request(scl_pin, "scl_pin");//管脚申请
    if (err) {
        PRK("[%d]gpio_request scl_pin failed.\n", __LINE__);
        return -1;
    }
    gpio_direction_output_value(scl_pin, 1);//该管脚设为输出，且输出为高电平

    err = gpio_request(sda_pin, "sda_pin");//管脚申请
    if (err) {
        PRK("[%d]gpio_request sda_pin failed.\n", __LINE__);
        return -1;
    }
    gpio_direction_output_value(sda_pin, 1);//该管脚设为输出，且输出为高电平
    
    PRK(KERN_INFO " op_pcf8574_open\n");
    return 0;
}

static int op_pcf8574_release(struct inode *inode, struct file *file)
{
    gpio_free(sda_pin);//释放IO口
    gpio_free(scl_pin);//释放IO口
    
    PRK(KERN_INFO " op_pcf8574_release\n");

    return 0;
}

static const struct file_operations pcf8574_fops =
{
    .owner   = THIS_MODULE,
    
    .open    = op_pcf8574_open,
    .read    = op_pcf8574_read,
    .write   = op_pcf8574_write,
    .release = op_pcf8574_release,
};

static struct miscdevice pcf8574_miscdev =
{
    //次设备号，驱动注册时，如果次号指定MISC_DYNAMIC_MINOR，则进行动态分配。
    .minor = MISC_DYNAMIC_MINOR,
    .name = DRV_NAME,//设备名称，将在/dev文件夹下显示
    .fops = &pcf8574_fops,
};



#define MODULE_VERS "1.0"  


#define MODULE_NAME "procfs_i2c"  

#define PROC_NAME "i2c"  

struct fb_data_t {  
    int type; 
    char name[4];  
    char value[4];  
};  
  

  
  
static struct proc_dir_entry *example_dir, *foo_file;    
  
struct fb_data_t foo_data;  


static char recv_msg[20];



static int proc_read_foobar(char *page, char **start,  
                off_t off, int count,   
                int *eof, void *data)  
{  
	u8	regAddr;
	int err = 0;
	int result;
	int len = sizeof(struct fb_data_t);  
	struct fb_data_t *fb_data = (struct fb_data_t *)data;  

	printk("jiangyibo read %d %x %x\n",fb_data->type,*(int *)fb_data->name,*(int *)fb_data->value);

    if(fb_data->type==1)
    {
        result = pcf8574_read(*(int *)fb_data->name);
    }else if(fb_data->type==2){
		scl_pin = GPIO_SLC_1;
        sda_pin = GPIO_SDA_1;
		result = pcf8574_read(*(int *)fb_data->name);
    }

	printk("jiangyibo %0x \n",result);
	
	fb_data->type=result;
	
    *(int *)page = result;
	//copy_to_user(page, &result, 4);
	return 4;


}  
  
  
static int proc_write_foobar(struct file *file,  
                 const char *buffer,  
                 unsigned long count,   
                 void *data)  
{  
		int len = 12;  
		struct fb_data_t *fb_data = (struct fb_data_t *)data;  
		
		if(copy_from_user(fb_data, buffer, 12))  
			return -EFAULT;  
		
		fb_data->value[len] = '\0';  
		
		return len;  

}  
  
static int __init pcf8574_init(void)//放后面，因为 misc_register 要包含前面的内容
{
/*
    int ret;

	printk("jiangyibo init\n");

    ret = misc_register(&pcf8574_miscdev);
    if (ret) {
        printk("misc_register error\n");
        return ret;
    }
    
    printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" init\n");
*/
	  int rv = 0;  
	  
		/* create directory */	
		example_dir = proc_mkdir(MODULE_NAME, NULL);  
		if(example_dir == NULL) {  
			rv = -ENOMEM;  
			goto out;  
		}  
	  
		/* create foo and bar files using same callback  
		 * functions   
		 */  
		foo_file = create_proc_entry(PROC_NAME, 0644, example_dir);  
		if(foo_file == NULL) {	
			rv = -ENOMEM;  
			goto no_foo;  
		}  
	  
		strcpy(foo_data.name, "foo");  
		strcpy(foo_data.value, "foo");	
		foo_file->data = &foo_data;  
		foo_file->read_proc = proc_read_foobar;  
    	foo_file->write_proc = proc_write_foobar;  

		op_pcf8574_init();
			 
		/* everything OK */  
		printk(KERN_INFO "%s %s initialised\n",  
			   MODULE_NAME, MODULE_VERS);  
		return 0;  
	  
	 
	no_foo:  
		remove_proc_entry("jiffies", example_dir);	
	 
	out:  
		return rv;	

}

module_init(pcf8574_init);

static void __exit pcf8574_exit(void)
{
/*    int ret;

    ret = misc_deregister(&pcf8574_miscdev);//注销
    if (ret) {
        printk("misc_deregister error\n");
        return ;
    }

    printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" exit\n");
    */
    remove_proc_entry(PROC_NAME, example_dir);    
    remove_proc_entry(MODULE_NAME, NULL);  
  
    printk(KERN_INFO "%s %s removed\n",  
           MODULE_NAME, MODULE_VERS); 

}
module_exit(pcf8574_exit);

MODULE_DESCRIPTION(DRV_DESC);//描述
MODULE_VERSION(DRV_VERSION);//版本
MODULE_AUTHOR("tingpan <smbx-ztbz@cnblogs.com>");//作者
MODULE_LICENSE("GPL v2");//协议


